using System;
using System.IO;
using System.Collections.Generic;

using PickGold.Exceptions;

namespace PickGold.Zip
{

	public partial class ZipFile
	{

		/// <summary>
		///   Delete file with retry on UnauthorizedAccessException.
		/// </summary>
		///
		/// <remarks>
		///   <para>
		///     When calling File.Delete() on a file that has been "recently"
		///     created, the call sometimes fails with
		///     UnauthorizedAccessException. This method simply retries the Delete 3
		///     times with a sleep between tries.
		///   </para>
		/// </remarks>
		///
		/// <param name='filename'>the name of the file to be deleted</param>
		private void DeleteFileWithRetry(string filename)
		{
			bool done = false;
			int nRetries = 3;
			for (int i = 0; i < nRetries && !done; i++)
			{
				try
				{
					File.Delete(filename);
					done = true;
				}
				catch (System.UnauthorizedAccessException)
				{
					Console.WriteLine("************************************************** Retry delete.");
					System.Threading.Thread.Sleep(200 + i * 200);
				}
			}
		}


		/// <summary>
		///   Saves the Zip archive to a file, specified by the Name property of the
		///   <c>ZipFile</c>.
		/// </summary>
		///
		/// <remarks>
		/// <para>
		///   The <c>ZipFile</c> instance is written to storage, typically a zip file
		///   in a filesystem, only when the caller calls <c>Save</c>.  In the typical
		///   case, the Save operation writes the zip content to a temporary file, and
		///   then renames the temporary file to the desired name. If necessary, this
		///   method will delete a pre-existing file before the rename.
		/// </para>
		///
		/// <para>
		///   The <see cref="ZipFile.Name"/> property is specified either explicitly,
		///   or implicitly using one of the parameterized ZipFile constructors.  For
		///   COM Automation clients, the <c>Name</c> property must be set explicitly,
		///   because COM Automation clients cannot call parameterized constructors.
		/// </para>
		///
		/// <para>
		///   When using a filesystem file for the Zip output, it is possible to call
		///   <c>Save</c> multiple times on the <c>ZipFile</c> instance. With each
		///   call the zip content is re-written to the same output file.
		/// </para>
		///
		/// <para>
		///   Data for entries that have been added to the <c>ZipFile</c> instance is
		///   written to the output when the <c>Save</c> method is called. This means
		///   that the input streams for those entries must be available at the time
		///   the application calls <c>Save</c>.  If, for example, the application
		///   adds entries with <c>AddEntry</c> using a dynamically-allocated
		///   <c>MemoryStream</c>, the memory stream must not have been disposed
		///   before the call to <c>Save</c>. See the <see
		///   cref="ZipEntry.InputStream"/> property for more discussion of the
		///   availability requirements of the input stream for an entry, and an
		///   approach for providing just-in-time stream lifecycle management.
		/// </para>
		///
		/// </remarks>
		///
		/// <seealso cref="PickGold.Zip.ZipFile.AddEntry(String, System.IO.Stream)"/>
		///
		/// <exception cref="PickGold.Zip.BadStateException">
		///   Thrown if you haven't specified a location or stream for saving the zip,
		///   either in the constructor or by setting the Name property, or if you try
		///   to save a regular zip archive to a filename with a .exe extension.
		/// </exception>
		///
		/// <exception cref="System.OverflowException">
		///   Thrown if <see cref="MaxOutputSegmentSize"/> is non-zero, and the number
		///   of segments that would be generated for the spanned zip file during the
		///   save operation exceeds 99.  If this happens, you need to increase the
		///   segment size.
		/// </exception>
		///
		public void Save()
		{
			try
			{
				bool thisSaveUsedZip64 = false;
				_saveOperationCanceled = false;
				_numberOfSegmentsForMostRecentSave = 0;
				OnSaveStarted();

				if (WriteStream == null)
					throw new BadStateException("You haven't specified where to save the zip.");

				if (_name != null && _name.EndsWith(".exe") && !_SavingSfx)
					throw new BadStateException("You specified an EXE for a plain zip file.");

				// check if modified, before saving.
				if (!_contentsChanged)
				{
					OnSaveCompleted();
					if (Verbose) StatusMessageTextWriter.WriteLine("No save is necessary....");
					return;
				}

				Reset(true);

				if (Verbose) StatusMessageTextWriter.WriteLine("saving....");

				// validate the number of entries
				if (_entries.Count >= 0xFFFF && _zip64 == Zip64Option.Never)
					throw new ZipException("The number of entries is 65535 or greater. Consider setting the UseZip64WhenSaving property on the ZipFile instance.");


				// write an entry in the zip for each file
				int n = 0;
				// workitem 9831
				ICollection<ZipEntry> c = (SortEntriesBeforeSaving) ? EntriesSorted : Entries;
				foreach (ZipEntry e in c) // _entries.Values
				{
					OnSaveEntry(n, e, true);
					e.Write(WriteStream);
					if (_saveOperationCanceled)
						break;

					n++;
					OnSaveEntry(n, e, false);
					if (_saveOperationCanceled)
						break;

					// Some entries can be skipped during the save.
					if (e.IncludedInMostRecentSave)
						thisSaveUsedZip64 |= e.OutputUsedZip64.Value;
				}



				if (_saveOperationCanceled)
					return;

				var zss = WriteStream as ZipSegmentedStream;

				_numberOfSegmentsForMostRecentSave = (zss != null)
					? zss.CurrentSegment
					: 1;

				bool directoryNeededZip64 =
					ZipOutput.WriteCentralDirectoryStructure
					(WriteStream,
					 c,
					 _numberOfSegmentsForMostRecentSave,
					 _zip64,
					 Comment,
					 new ZipContainer(this));

				OnSaveEvent(ZipProgressEventType.Saving_AfterSaveTempArchive);

				_hasBeenSaved = true;
				_contentsChanged = false;

				thisSaveUsedZip64 |= directoryNeededZip64;
				_OutputUsesZip64 = new Nullable<bool>(thisSaveUsedZip64);


				// do the rename as necessary
				if (_name != null &&
					(_temporaryFileName != null || zss != null))
				{
					// _temporaryFileName may remain null if we are writing to a stream.
					// only close the stream if there is a file behind it.
#if NETCF
                    WriteStream.Close();
#else
					WriteStream.Dispose();
#endif
					if (_saveOperationCanceled)
						return;

					if (_fileAlreadyExists && this._readstream != null)
					{
						// This means we opened and read a zip file.
						// If we are now saving to the same file, we need to close the
						// orig file, first.
						this._readstream.Close();
						this._readstream = null;
						// the archiveStream for each entry needs to be null
						foreach (var e in c)
						{
							var zss1 = e._archiveStream as ZipSegmentedStream;
							if (zss1 != null)
#if NETCF
                                zss1.Close();
#else
								zss1.Dispose();
#endif
							e._archiveStream = null;
						}
					}

					string tmpName = null;
					if (File.Exists(_name))
					{
						// the steps:
						//
						// 1. Delete tmpName
						// 2. move existing zip to tmpName
						// 3. rename (File.Move) working file to name of existing zip
						// 4. delete tmpName
						//
						// This series of steps avoids the exception,
						// System.IO.IOException:
						//   "Cannot create a file when that file already exists."
						//
						// Cannot just call File.Replace() here because
						// there is a possibility that the TEMP volume is different
						// that the volume for the final file (c:\ vs d:\).
						// So we need to do a Delete+Move pair.
						//
						// But, when doing the delete, Windows allows a process to
						// delete the file, even though it is held open by, say, a
						// virus scanner. It gets internally marked as "delete
						// pending". The file does not actually get removed from the
						// file system, it is still there after the File.Delete
						// call.
						//
						// Therefore, we need to move the existing zip, which may be
						// held open, to some other name. Then rename our working
						// file to the desired name, then delete (possibly delete
						// pending) the "other name".
						//
						// Ideally this would be transactional. It's possible that the
						// delete succeeds and the move fails. Lacking transactions, if
						// this kind of failure happens, we're hosed, and this logic will
						// throw on the next File.Move().
						//
						//File.Delete(_name);
						// workitem 10447
#if NETCF || SILVERLIGHT
                        tmpName = _name + "." + SharedUtilities.GenerateRandomStringImpl(8,0) + ".tmp";
#else
						tmpName = _name + "." + Path.GetRandomFileName();
#endif
						if (File.Exists(tmpName))
							DeleteFileWithRetry(tmpName);
						File.Move(_name, tmpName);
					}

					OnSaveEvent(ZipProgressEventType.Saving_BeforeRenameTempArchive);
					File.Move((zss != null) ? zss.CurrentTempName : _temporaryFileName,
							  _name);

					OnSaveEvent(ZipProgressEventType.Saving_AfterRenameTempArchive);

					if (tmpName != null)
					{
						try
						{
							// not critical
							if (File.Exists(tmpName))
								File.Delete(tmpName);
						}
						catch
						{
							// don't care about exceptions here.
						}

					}
					_fileAlreadyExists = true;
				}

				NotifyEntriesSaveComplete(c);
				OnSaveCompleted();
				_JustSaved = true;
			}

			// workitem 5043
			finally
			{
				CleanupAfterSaveOperation();
			}

			return;
		}



		private static void NotifyEntriesSaveComplete(ICollection<ZipEntry> c)
		{
			foreach (ZipEntry e in c)
			{
				e.NotifySaveComplete();
			}
		}


		private void RemoveTempFile()
		{
			try
			{
				if (File.Exists(_temporaryFileName))
				{
					File.Delete(_temporaryFileName);
				}
			}
			catch (IOException ex1)
			{
				if (Verbose)
					StatusMessageTextWriter.WriteLine("ZipFile::Save: could not delete temp file: {0}.", ex1.Message);
			}
		}


		private void CleanupAfterSaveOperation()
		{
			if (_name != null)
			{
				// close the stream if there is a file behind it.
				if (_writestream != null)
				{
					try
					{
						// workitem 7704
#if NETCF
                        _writestream.Close();
#else
						_writestream.Dispose();
#endif
					}
					catch (System.IO.IOException) { }
				}
				_writestream = null;

				if (_temporaryFileName != null)
				{
					RemoveTempFile();
					_temporaryFileName = null;
				}
			}
		}


		/// <summary>
		/// Save the file to a new zipfile, with the given name.
		/// </summary>
		///
		/// <remarks>
		/// <para>
		/// This method allows the application to explicitly specify the name of the zip
		/// file when saving. Use this when creating a new zip file, or when
		/// updating a zip archive.
		/// </para>
		///
		/// <para>
		/// An application can also save a zip archive in several places by calling this
		/// method multiple times in succession, with different filenames.
		/// </para>
		///
		/// <para>
		/// The <c>ZipFile</c> instance is written to storage, typically a zip file in a
		/// filesystem, only when the caller calls <c>Save</c>.  The Save operation writes
		/// the zip content to a temporary file, and then renames the temporary file
		/// to the desired name. If necessary, this method will delete a pre-existing file
		/// before the rename.
		/// </para>
		///
		/// </remarks>
		///
		/// <exception cref="System.ArgumentException">
		/// Thrown if you specify a directory for the filename.
		/// </exception>
		///
		/// <param name="fileName">
		/// The name of the zip archive to save to. Existing files will
		/// be overwritten with great prejudice.
		/// </param>
		///
		/// <example>
		/// This example shows how to create and Save a zip file.
		/// <code>
		/// using (ZipFile zip = new ZipFile())
		/// {
		///   zip.AddDirectory(@"c:\reports\January");
		///   zip.Save("January.zip");
		/// }
		/// </code>
		///
		/// <code lang="VB">
		/// Using zip As New ZipFile()
		///   zip.AddDirectory("c:\reports\January")
		///   zip.Save("January.zip")
		/// End Using
		/// </code>
		///
		/// </example>
		///
		/// <example>
		/// This example shows how to update a zip file.
		/// <code>
		/// using (ZipFile zip = ZipFile.Read("ExistingArchive.zip"))
		/// {
		///   zip.AddFile("NewData.csv");
		///   zip.Save("UpdatedArchive.zip");
		/// }
		/// </code>
		///
		/// <code lang="VB">
		/// Using zip As ZipFile = ZipFile.Read("ExistingArchive.zip")
		///   zip.AddFile("NewData.csv")
		///   zip.Save("UpdatedArchive.zip")
		/// End Using
		/// </code>
		///
		/// </example>
		public void Save(String fileName)
		{
			// Check for the case where we are re-saving a zip archive
			// that was originally instantiated with a stream.  In that case,
			// the _name will be null. If so, we set _writestream to null,
			// which insures that we'll cons up a new WriteStream (with a filesystem
			// file backing it) in the Save() method.
			if (_name == null)
				_writestream = null;

			else _readName = _name; // workitem 13915

			_name = fileName;
			if (Directory.Exists(_name))
				throw new ZipException("Bad Directory", new System.ArgumentException("That name specifies an existing directory. Please specify a filename.", "fileName"));
			_contentsChanged = true;
			_fileAlreadyExists = File.Exists(_name);
			Save();
		}


		/// <summary>
		///   Save the zip archive to the specified stream.
		/// </summary>
		///
		/// <remarks>
		/// <para>
		///   The <c>ZipFile</c> instance is written to storage - typically a zip file
		///   in a filesystem, but using this overload, the storage can be anything
		///   accessible via a writable stream - only when the caller calls <c>Save</c>.
		/// </para>
		///
		/// <para>
		///   Use this method to save the zip content to a stream directly.  A common
		///   scenario is an ASP.NET application that dynamically generates a zip file
		///   and allows the browser to download it. The application can call
		///   <c>Save(Response.OutputStream)</c> to write a zipfile directly to the
		///   output stream, without creating a zip file on the disk on the ASP.NET
		///   server.
		/// </para>
		///
		/// <para>
		///   Be careful when saving a file to a non-seekable stream, including
		///   <c>Response.OutputStream</c>. When DotNetZip writes to a non-seekable
		///   stream, the zip archive is formatted in such a way that may not be
		///   compatible with all zip tools on all platforms.  It's a perfectly legal
		///   and compliant zip file, but some people have reported problems opening
		///   files produced this way using the Mac OS archive utility.
		/// </para>
		///
		/// </remarks>
		///
		/// <example>
		///
		///   This example saves the zipfile content into a MemoryStream, and
		///   then gets the array of bytes from that MemoryStream.
		///
		/// <code lang="C#">
		/// using (var zip = new PickGold.Zip.ZipFile())
		/// {
		///     zip.CompressionLevel= PickGold.Zlib.CompressionLevel.BestCompression;
		///     zip.Password = "VerySecret.";
		///     zip.Encryption = EncryptionAlgorithm.WinZipAes128;
		///     zip.AddFile(sourceFileName);
		///     MemoryStream output = new MemoryStream();
		///     zip.Save(output);
		///
		///     byte[] zipbytes = output.ToArray();
		/// }
		/// </code>
		/// </example>
		///
		/// <example>
		/// <para>
		///   This example shows a pitfall you should avoid. DO NOT read
		///   from a stream, then try to save to the same stream.  DO
		///   NOT DO THIS:
		/// </para>
		///
		/// <code lang="C#">
		/// using (var fs = new FileSteeam(filename, FileMode.Open))
		/// {
		///   using (var zip = PickGold.Zip.ZipFile.Read(inputStream))
		///   {
		///     zip.AddEntry("Name1.txt", "this is the content");
		///     zip.Save(inputStream);  // NO NO NO!!
		///   }
		/// }
		/// </code>
		///
		/// <para>
		///   Better like this:
		/// </para>
		///
		/// <code lang="C#">
		/// using (var zip = PickGold.Zip.ZipFile.Read(filename))
		/// {
		///     zip.AddEntry("Name1.txt", "this is the content");
		///     zip.Save();  // YES!
		/// }
		/// </code>
		///
		/// </example>
		///
		/// <param name="outputStream">
		///   The <c>System.IO.Stream</c> to write to. It must be
		///   writable. If you created the ZipFile instanct by calling
		///   ZipFile.Read(), this stream must not be the same stream
		///   you passed to ZipFile.Read().
		/// </param>
		public void Save(Stream outputStream)
		{
			if (outputStream == null)
				throw new ArgumentNullException("outputStream");
			if (!outputStream.CanWrite)
				throw new ArgumentException("Must be a writable stream.", "outputStream");

			// if we had a filename to save to, we are now obliterating it.
			_name = null;

			_writestream = new CountingStream(outputStream);

			_contentsChanged = true;
			_fileAlreadyExists = false;
			Save();
		}


	}



	internal static class ZipOutput
	{
		public static bool WriteCentralDirectoryStructure(Stream s, ICollection<ZipEntry> entries, uint numSegments, Zip64Option zip64, string comment, ZipContainer container)
		{
			var zss = s as ZipSegmentedStream;
			if (zss != null)
				zss.ContiguousWrite = true;

			// write to a memory stream in order to keep the
			// CDR contiguous
			var aLength = 0L;
			using (var ms = new MemoryStream())
			{
				foreach (var e in entries)
				{
					if (e.IncludedInMostRecentSave)
					{
						// this writes a ZipDirEntry corresponding to the ZipEntry
						e.WriteCentralDirectoryEntry(ms);
					}
				}
				var a = ms.ToArray();
				s.Write(a, 0, a.Length);
				aLength = a.Length;
			}


			// We need to keep track of the start and
			// Finish of the Central Directory Structure.

			// Cannot always use WriteStream.Length or Position; some streams do
			// not support these. (eg, ASP.NET Response.OutputStream) In those
			// cases we have a CountingStream.

			// Also, we cannot just set Start as s.Position bfore the write, and Finish
			// as s.Position after the write.  In a split zip, the write may actually
			// flip to the next segment.  In that case, Start will be zero.  But we
			// don't know that til after we know the size of the thing to write.  So the
			// answer is to compute the directory, then ask the ZipSegmentedStream which
			// segment that directory would fall in, it it were written.  Then, include
			// that data into the directory, and finally, write the directory to the
			// output stream.

			var output = s as CountingStream;
			var Finish = (output != null) ? output.ComputedPosition : s.Position;  // BytesWritten
			var Start = Finish - aLength;

			// need to know which segment the EOCD record starts in
			var startSegment = (zss != null)
				? zss.CurrentSegment
				: 0U;

			var SizeOfCentralDirectory = Finish - Start;

			var countOfEntries = CountEntries(entries);

			var needZip64CentralDirectory =
				zip64 == Zip64Option.Always ||
				countOfEntries >= 0xFFFF ||
				SizeOfCentralDirectory > 0xFFFFFFFF ||
				Start > 0xFFFFFFFF;

			var a2 = Reflector.Generic<byte>.NullArray;

			// emit ZIP64 extensions as required
			if (needZip64CentralDirectory)
			{
				if (zip64 == Zip64Option.Never)
				{
#if NETCF
                    throw new ZipException("The archive requires a ZIP64 Central Directory. Consider enabling ZIP64 extensions.");
#else
					System.Diagnostics.StackFrame sf = new System.Diagnostics.StackFrame(1);
					if (sf.GetMethod().DeclaringType == typeof(ZipFile))
						throw new ZipException("The archive requires a ZIP64 Central Directory. Consider setting the ZipFile.UseZip64WhenSaving property.");
					else
						throw new ZipException("The archive requires a ZIP64 Central Directory. Consider setting the ZipOutputStream.EnableZip64 property.");
#endif

				}

				var a = GenZip64EndOfCentralDirectory(Start, Finish, countOfEntries, numSegments);
				a2 = GenCentralDirectoryFooter(Start, Finish, zip64, countOfEntries, comment, container);
				if (startSegment != 0)
				{
					UInt32 thisSegment = zss.ComputeSegment(a.Length + a2.Length);
					int i = 16;
					// number of this disk
					Array.Copy(BitConverter.GetBytes(thisSegment), 0, a, i, 4);
					i += 4;
					// number of the disk with the start of the central directory
					//Array.Copy(BitConverter.GetBytes(startSegment), 0, a, i, 4);
					Array.Copy(BitConverter.GetBytes(thisSegment), 0, a, i, 4);

					i = 60;
					// offset 60
					// number of the disk with the start of the zip64 eocd
					Array.Copy(BitConverter.GetBytes(thisSegment), 0, a, i, 4);
					i += 4;
					i += 8;

					// offset 72
					// total number of disks
					Array.Copy(BitConverter.GetBytes(thisSegment), 0, a, i, 4);
				}
				s.Write(a, 0, a.Length);
			}
			else
				a2 = GenCentralDirectoryFooter(Start, Finish, zip64, countOfEntries, comment, container);


			// now, the regular footer
			if (startSegment != 0)
			{
				// The assumption is the central directory is never split across
				// segment boundaries.

				UInt16 thisSegment = (UInt16)zss.ComputeSegment(a2.Length);
				int i = 4;
				// number of this disk
				Array.Copy(BitConverter.GetBytes(thisSegment), 0, a2, i, 2);
				i += 2;
				// number of the disk with the start of the central directory
				//Array.Copy(BitConverter.GetBytes((UInt16)startSegment), 0, a2, i, 2);
				Array.Copy(BitConverter.GetBytes(thisSegment), 0, a2, i, 2);
				i += 2;
			}

			s.Write(a2, 0, a2.Length);

			// reset the contiguous write property if necessary
			if (zss != null)
				zss.ContiguousWrite = false;

			return needZip64CentralDirectory;
		}


		private static System.Text.Encoding GetEncoding(ZipContainer container, string t)
		{
			switch (container.AlternateEncodingUsage)
			{
				case ZipOption.Always:
					return container.AlternateEncoding;
				case ZipOption.Never:
					return container.DefaultEncoding;
			}

			// AsNecessary is in force
			var e = container.DefaultEncoding;
			if (t == null) return e;

			var bytes = e.GetBytes(t);
			var t2 = e.GetString(bytes, 0, bytes.Length);
			if (t2.Equals(t)) return e;
			return container.AlternateEncoding;
		}



		private static byte[] GenCentralDirectoryFooter(long StartOfCentralDirectory,
														long EndOfCentralDirectory,
														Zip64Option zip64,
														int entryCount,
														string comment,
														ZipContainer container)
		{
			System.Text.Encoding encoding = GetEncoding(container, comment);
			int j = 0;
			int bufferLength = 22;
			byte[] block = null;
			Int16 commentLength = 0;
			if ((comment != null) && (comment.Length != 0))
			{
				block = encoding.GetBytes(comment);
				commentLength = (Int16)block.Length;
			}
			bufferLength += commentLength;
			byte[] bytes = new byte[bufferLength];

			int i = 0;
			// signature
			byte[] sig = BitConverter.GetBytes(ZipConstants.EndOfCentralDirectorySignature);
			Array.Copy(sig, 0, bytes, i, 4);
			i += 4;

			// number of this disk
			// (this number may change later)
			bytes[i++] = 0;
			bytes[i++] = 0;

			// number of the disk with the start of the central directory
			// (this number may change later)
			bytes[i++] = 0;
			bytes[i++] = 0;

			// handle ZIP64 extensions for the end-of-central-directory
			if (entryCount >= 0xFFFF || zip64 == Zip64Option.Always)
			{
				// the ZIP64 version.
				for (j = 0; j < 4; j++)
					bytes[i++] = 0xFF;
			}
			else
			{
				// the standard version.
				// total number of entries in the central dir on this disk
				bytes[i++] = (byte)(entryCount & 0x00FF);
				bytes[i++] = (byte)((entryCount & 0xFF00) >> 8);

				// total number of entries in the central directory
				bytes[i++] = (byte)(entryCount & 0x00FF);
				bytes[i++] = (byte)((entryCount & 0xFF00) >> 8);
			}

			// size of the central directory
			Int64 SizeOfCentralDirectory = EndOfCentralDirectory - StartOfCentralDirectory;

			if (SizeOfCentralDirectory >= 0xFFFFFFFF || StartOfCentralDirectory >= 0xFFFFFFFF)
			{
				// The actual data is in the ZIP64 central directory structure
				for (j = 0; j < 8; j++)
					bytes[i++] = 0xFF;
			}
			else
			{
				// size of the central directory (we just get the low 4 bytes)
				bytes[i++] = (byte)(SizeOfCentralDirectory & 0x000000FF);
				bytes[i++] = (byte)((SizeOfCentralDirectory & 0x0000FF00) >> 8);
				bytes[i++] = (byte)((SizeOfCentralDirectory & 0x00FF0000) >> 16);
				bytes[i++] = (byte)((SizeOfCentralDirectory & 0xFF000000) >> 24);

				// offset of the start of the central directory (we just get the low 4 bytes)
				bytes[i++] = (byte)(StartOfCentralDirectory & 0x000000FF);
				bytes[i++] = (byte)((StartOfCentralDirectory & 0x0000FF00) >> 8);
				bytes[i++] = (byte)((StartOfCentralDirectory & 0x00FF0000) >> 16);
				bytes[i++] = (byte)((StartOfCentralDirectory & 0xFF000000) >> 24);
			}


			// zip archive comment
			if ((comment == null) || (comment.Length == 0))
			{
				// no comment!
				bytes[i++] = (byte)0;
				bytes[i++] = (byte)0;
			}
			else
			{
				// the size of our buffer defines the max length of the comment we can write
				if (commentLength + i + 2 > bytes.Length) commentLength = (Int16)(bytes.Length - i - 2);
				bytes[i++] = (byte)(commentLength & 0x00FF);
				bytes[i++] = (byte)((commentLength & 0xFF00) >> 8);

				if (commentLength != 0)
				{
					// now actually write the comment itself into the byte buffer
					for (j = 0; (j < commentLength) && (i + j < bytes.Length); j++)
					{
						bytes[i + j] = block[j];
					}
					i += j;
				}
			}

			//   s.Write(bytes, 0, i);
			return bytes;
		}



		private static byte[] GenZip64EndOfCentralDirectory(long StartOfCentralDirectory,
															long EndOfCentralDirectory,
															int entryCount,
															uint numSegments)
		{
			const int bufferLength = 12 + 44 + 20;

			byte[] bytes = new byte[bufferLength];

			int i = 0;
			// signature
			byte[] sig = BitConverter.GetBytes(ZipConstants.Zip64EndOfCentralDirectoryRecordSignature);
			Array.Copy(sig, 0, bytes, i, 4);
			i += 4;

			// There is a possibility to include "Extensible" data in the zip64
			// end-of-central-dir record.  I cannot figure out what it might be used to
			// store, so the size of this record is always fixed.  Maybe it is used for
			// strong encryption data?  That is for another day.
			long DataSize = 44;
			Array.Copy(BitConverter.GetBytes(DataSize), 0, bytes, i, 8);
			i += 8;

			// offset 12
			// VersionMadeBy = 45;
			bytes[i++] = 45;
			bytes[i++] = 0x00;

			// VersionNeededToExtract = 45;
			bytes[i++] = 45;
			bytes[i++] = 0x00;

			// offset 16
			// number of the disk, and the disk with the start of the central dir.
			// (this may change later)
			for (int j = 0; j < 8; j++)
				bytes[i++] = 0x00;

			// offset 24
			long numberOfEntries = entryCount;
			Array.Copy(BitConverter.GetBytes(numberOfEntries), 0, bytes, i, 8);
			i += 8;
			Array.Copy(BitConverter.GetBytes(numberOfEntries), 0, bytes, i, 8);
			i += 8;

			// offset 40
			Int64 SizeofCentraldirectory = EndOfCentralDirectory - StartOfCentralDirectory;
			Array.Copy(BitConverter.GetBytes(SizeofCentraldirectory), 0, bytes, i, 8);
			i += 8;
			Array.Copy(BitConverter.GetBytes(StartOfCentralDirectory), 0, bytes, i, 8);
			i += 8;

			// offset 56
			// now, the locator
			// signature
			sig = BitConverter.GetBytes(ZipConstants.Zip64EndOfCentralDirectoryLocatorSignature);
			Array.Copy(sig, 0, bytes, i, 4);
			i += 4;

			// offset 60
			// number of the disk with the start of the zip64 eocd
			// (this will change later)  (it will?)
			uint x2 = (numSegments == 0) ? 0 : (uint)(numSegments - 1);
			Array.Copy(BitConverter.GetBytes(x2), 0, bytes, i, 4);
			i += 4;

			// offset 64
			// relative offset of the zip64 eocd
			Array.Copy(BitConverter.GetBytes(EndOfCentralDirectory), 0, bytes, i, 8);
			i += 8;

			// offset 72
			// total number of disks
			// (this will change later)
			Array.Copy(BitConverter.GetBytes(numSegments), 0, bytes, i, 4);
			i += 4;

			return bytes;
		}



		private static int CountEntries(ICollection<ZipEntry> _entries)
		{
			// Cannot just emit _entries.Count, because some of the entries
			// may have been skipped.
			int count = 0;
			foreach (var entry in _entries)
				if (entry.IncludedInMostRecentSave) count++;
			return count;
		}




	}
}
